﻿using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Xml;
using BuzzX8.Xmpp.StreamElements;

namespace BuzzX8.Xmpp.Connectivity
{
    public class NetworkChannel : IXmppChannel, IDisposable
    {
        private XmppStreamReader reader;
        private XmppStreamWriter writer;
        private Socket socket;

        private XmlReaderStreamAdapter receiveBuffer;
        private MemoryStream sendBuffer;

        public NetworkChannel()
        {
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            Initialize();
        }

        public NetworkChannel(Socket socket)
        {
            if (socket == null) throw new ArgumentNullException("socket");
            this.socket = socket;
            Initialize();
        }

        private void Initialize()
        {
            receiveBuffer = new XmlReaderStreamAdapter(socket);
            sendBuffer = new MemoryStream();

            writer = new XmppStreamWriter(sendBuffer);
            reader = new XmppStreamReader(receiveBuffer);
        }

        public string HostName { get; set; }

        public int Port { get; set; }
        
        public bool IsOpen
        {
            get { return socket.Connected; }
        }

        public void Open()
        {
            Open(HostName, Port);
        }

        public void Open(string hostName, int port)
        {
            IPAddress[] hostAddresses = Dns.GetHostAddresses(hostName);
            if (hostAddresses.Any(a => a.AddressFamily == socket.AddressFamily))
                Open(hostAddresses.First(a => a.AddressFamily == socket.AddressFamily), port);
            else throw new InvalidOperationException();
        }

        public void Open(IPAddress address, int port)
        {
            if (IsOpen) throw new InvalidOperationException();
            socket.Connect(address, port);
        }

        public void Close()
        {
            Dispose();
        }

        public StreamElement Receive()
        {
            return reader.Read();
        }

        public void Send(StreamElement element)
        {
            writer.Write(element);
            socket.Send(sendBuffer.ToArray());
        }

        public void Dispose()
        {
            
        }
    }

    internal class XmlReaderStreamAdapter : Stream
    {
        private Socket socket;
        private int position;

        public XmlReaderStreamAdapter(Socket socket)
        {
            this.socket = socket;
            position = 0;
        }

        public override bool CanRead
        {
            get { return true; }
        }

        public override bool CanSeek
        {
            get { return false; }
        }

        public override bool CanWrite
        {
            get { return false; }
        }

        public override void Flush()
        {
            throw new NotImplementedException();
        }

        public override long Length
        {
            get { throw new NotImplementedException(); }
        }

        public override long Position
        {
            get { return position; }
            set { throw new InvalidOperationException(); }
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            int result = socket.Receive(buffer, offset, socket.Available, SocketFlags.None);
            position += result;
            return result;
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new InvalidOperationException();
        }

        public override void SetLength(long value)
        {
            throw new InvalidOperationException();
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new InvalidOperationException();
        }
    }
}
